// COLMAP - Structure-from-Motion and Multi-View Stereo.
// Copyright (C) 2016  Johannes L. Schoenberger <jsch at inf.ethz.ch>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "util/string.h"

#include <cstdarg>
#include <fstream>
#include <sstream>
#include <stdarg.h>
#include <stdio.h>

#include <boost/algorithm/string.hpp>

namespace RVG
{
namespace
{

void StringAppendV(std::string* dst, const char* format, va_list ap)
{
    // First try with a small fixed size buffer.
    static const int kFixedBufferSize = 1024;
    char fixed_buffer[kFixedBufferSize];

    // It is possible for methods that use a va_list to invalidate
    // the data in it upon use.  The fix is to make a copy
    // of the structure before using it and use that copy instead.
    va_list backup_ap;
    va_copy(backup_ap, ap);
    int result = vsnprintf(fixed_buffer, kFixedBufferSize, format, backup_ap);
    va_end(backup_ap);

    if (result < kFixedBufferSize)
    {
        if (result >= 0)
        {
            // Normal case - everything fits.
            dst->append(fixed_buffer, result);
            return;
        }

#ifdef _MSC_VER
        // Error or MSVC running out of space.  MSVC 8.0 and higher
        // can be asked about space needed with the special idiom below:
        va_copy(backup_ap, ap);
        result = vsnprintf(nullptr, 0, format, backup_ap);
        va_end(backup_ap);
#endif

        if (result < 0)
        {
            // Just an error.
            return;
        }
    }

    // Increase the buffer size to the size requested by vsnprintf,
    // plus one for the closing \0.
    const int variable_buffer_size = result + 1;
    std::unique_ptr<char> variable_buffer(new char[variable_buffer_size]);

    // Restore the va_list before we use it again.
    va_copy(backup_ap, ap);
    result =
        vsnprintf(variable_buffer.get(), variable_buffer_size, format, backup_ap);
    va_end(backup_ap);

    if (result >= 0 && result < variable_buffer_size)
    {
        dst->append(variable_buffer.get(), result);
    }
}

bool IsNotWhiteSpace(const int character)
{
    return character != ' ' && character != '\n' && character != '\r' &&
           character != '\t';
}

}  // namespace

std::string StringPrintf(const char* format, ...)
{
    va_list ap;
    va_start(ap, format);
    std::string result;
    StringAppendV(&result, format, ap);
    va_end(ap);
    return result;
}

std::string StringReplace(const std::string& str, const std::string& old_str,
                          const std::string& new_str)
{
    if (old_str.empty())
    {
        return str;
    }
    size_t position = 0;
    std::string mod_str = str;
    while ((position = mod_str.find(old_str, position)) != std::string::npos)
    {
        mod_str.replace(position, old_str.size(), new_str);
        position += new_str.size();
    }
    return mod_str;
}

std::vector<std::string> StringSplit(const std::string& str,
                                     const std::string& delim)
{
    std::vector<std::string> elems;
    boost::split(elems, str, boost::is_any_of(delim), boost::token_compress_on);
    return elems;
}

bool StringStartsWith(const std::string& str, const std::string& prefix)
{
    return !prefix.empty() && prefix.size() <= str.size() &&
           str.substr(0, prefix.size()) == prefix;
}

void StringLeftTrim(std::string* str)
{
    str->erase(str->begin(),
               std::find_if(str->begin(), str->end(), IsNotWhiteSpace));
}

void StringRightTrim(std::string* str)
{
    str->erase(std::find_if(str->rbegin(), str->rend(), IsNotWhiteSpace).base(),
               str->end());
}

void StringTrim(std::string* str)
{
    StringLeftTrim(str);
    StringRightTrim(str);
}

}  // namespace colmap
